/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * <http://www.mongodb.com/licensing/server-side-public-license>.
 */

import type React from 'react';
import type * as Immutable from 'immutable';
import type { FormikErrors } from 'formik';

import type { ExportPayload } from 'util/MessagesExportUtils';
import type { IconName } from 'components/common/Icon';
import type Widget from 'views/logic/widgets/Widget';
import type { ActionDefinition } from 'views/components/actions/ActionHandler';
import type { VisualizationComponent } from 'views/components/aggregationbuilder/AggregationBuilder';
import type { WidgetActionType } from 'views/components/widgets/Types';
import type { Creator } from 'views/components/sidebar/create/AddWidgetButton';
import type { ViewHook } from 'views/logic/hooks/ViewHook';
import type WidgetConfig from 'views/logic/widgets/WidgetConfig';
import type FieldTypeMapping from 'views/logic/fieldtypes/FieldTypeMapping';
import type { Completer } from 'views/components/searchbar/SearchBarAutocompletions';
import type { Result } from 'views/components/widgets/Widget';
import type { OverrideProps } from 'views/components/WidgetOverrideElements';
import type {
  VisualizationConfigDefinition,
  VisualizationConfigFormValues,
  VisualizationFormValues,
  WidgetConfigFormValues,
} from 'views/components/aggregationwizard';
import type VisualizationConfig from 'views/logic/aggregationbuilder/visualizations/VisualizationConfig';
import type Query from 'views/logic/queries/Query';
import type { TimeRange, NoTimeRangeOverride, AbsoluteTimeRange, QueryId } from 'views/logic/queries/Query';
import type View from 'views/logic/views/View';
import type User from 'logic/users/User';
import type { Message } from 'views/components/messagelist/Types';
import type { ValuePath } from 'views/logic/valueactions/ValueActionHandler';
import type WidgetPosition from 'views/logic/widgets/WidgetPosition';
import type MessagesWidgetConfig from 'views/logic/widgets/MessagesWidgetConfig';
import type { ValidationExplanations } from 'views/components/searchbar/queryvalidation/types';
import type { CustomCommand, CustomCommandContext } from 'views/components/searchbar/queryinput/types';
import type SearchExecutionState from 'views/logic/search/SearchExecutionState';
import type { ParameterBindings } from 'views/logic/search/SearchExecutionState';
import type SearchMetadata from 'views/logic/search/SearchMetadata';
import type { ViewsDispatch } from 'views/stores/useViewsDispatch';
import type SearchResult from 'views/logic/SearchResult';
import type Parameter from 'views/logic/parameters/Parameter';
import type { UndoRedoState } from 'views/logic/slices/undoRedoSlice';
import type { SearchExecutors } from 'views/logic/slices/searchExecutionSlice';
import type { JobIds } from 'views/stores/SearchJobs';
import type { FilterComponents, Attributes } from 'views/components/widgets/overview-configuration/filters/types';
import type { Event } from 'components/events/events/types';
import type { PluggableReducer } from 'store';
import type { WidgetMapping } from 'views/logic/views/types';
import type { ValueRendererProps } from 'views/components/messagelist/decoration/ValueRenderer';
import type { EntityPermissionsMapper } from 'logic/permissions/EntityPermissionsMapper';
import type { WidgetsState } from 'views/logic/slices/widgetsSlice';
import type { FieldTypeMappingsList } from 'views/logic/fieldtypes/types';
import type { DEFAULT_AXIS_KEY } from 'views/components/visualizations/Constants';

export type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends readonly (infer ElementType)[]
  ? ElementType
  : never;

export type BackendWidgetPosition = {
  id: string;
  col: number;
  row: number;
  height: number;
  width: number;
};

export type WidgetPositions = { [widgetId: string]: WidgetPosition };

export interface EditWidgetComponentProps<Config extends WidgetConfig = WidgetConfig> {
  children: React.ReactNode;
  config: Config;
  editing: boolean;
  id: string;
  type: string;
  fields: Immutable.List<FieldTypeMapping>;
  onChange: (newConfig: Config) => void;
  onCancel: () => void;
}

export interface WidgetResults {
  [key: string]: Result;
}

export interface WidgetComponentProps<Config extends WidgetConfig = WidgetConfig, Results = WidgetResults> {
  config: Config;
  data: Results;
  editing: boolean;
  fields: Immutable.List<FieldTypeMapping>;
  filter?: string;
  queryId: string;
  onConfigChange?: (newConfig: Config) => Promise<void>;
  setLoadingState: (loading: boolean) => void;
  title?: string;
  toggleEdit?: () => void;
  type?: string;
  id: string;
  height: number;
  width: number;
}

export interface WidgetExport<T extends WidgetConfig = WidgetConfig> {
  type: string;
  displayName?: string;
  defaultHeight?: number;
  defaultWidth?: number;
  visualizationComponent: React.ComponentType<WidgetComponentProps<T, any>>;
  editComponent: React.ComponentType<EditWidgetComponentProps<T>>;
  hasEditSubmitButton?: boolean;
  needsControlledHeight: (widget: { config: Widget['config'] }) => boolean;
  searchResultTransformer?: (data: Array<unknown>) => unknown;
  searchTypes: (widget: Widget) => Array<any>;
  titleGenerator?: (widget: { config: Widget['config'] }) => string;
  exportComponent?: React.ComponentType<{ widget: Widget }>;
}

type BaseField = {
  title: string;
  name: string;
  helpComponent?: React.ComponentType;
  inputHelp?: (formValues: VisualizationConfigFormValues, widgetConfigFormValues: WidgetConfigFormValues) => string;
  description?: string;
  isShown?: (formValues: VisualizationConfigFormValues, widgetConfigFormValues?: WidgetConfigFormValues) => boolean;
};

type BaseRequiredField = BaseField & {
  required: boolean;
};

type SelectField = BaseRequiredField & {
  type: 'select';
  options: ReadonlyArray<string | [string, any]>;
};

type MultiSelectField = BaseRequiredField & {
  type: 'multi-select';
  options: ((props: any) => ReadonlyArray<string | [string, any]>) | ReadonlyArray<string | [string, any]>;
};

type BooleanField = BaseField & {
  type: 'boolean';
};

export type NumericField = BaseRequiredField & {
  type: 'numeric';
  step?: string;
};

export type TextField = BaseField & {
  type: 'text';
};

export type CustomField = BaseField & {
  type: 'custom';
  id: string;
  component: React.ComponentType<CustomFieldComponentProps>;
};

export type CustomFieldComponentProps = {
  field: CustomField;
  name: string;
  title: React.ReactElement;
  id: string;
  inputHelp?: string;
};

export type ConfigurationField = SelectField | BooleanField | NumericField | MultiSelectField | TextField | CustomField;

export interface VisualizationCapabilities {
  'event-annotations': undefined;
}

export type VisualizationCapability = keyof VisualizationCapabilities;

export interface VisualizationType<
  Type extends string,
  ConfigType extends VisualizationConfig = VisualizationConfig,
  ConfigFormValuesType extends VisualizationConfigFormValues = VisualizationConfigFormValues,
> {
  type: string;
  displayName: string;
  component: VisualizationComponent<Type>;
  config?: VisualizationConfigDefinition<ConfigType, ConfigFormValuesType>;
  capabilities?: Array<VisualizationCapability>;
  validate?: (formValues: WidgetConfigFormValues) => FormikErrors<VisualizationFormValues>;
}

interface ResultHandler<T, R> {
  convert: (result: T) => R;
}

interface SearchType<T, R> {
  type: string;
  handler: ResultHandler<T, R>;
  defaults: {};
}

export interface ExportFormat {
  order?: number;
  type: string;
  displayName: () => string;
  disabled?: () => boolean;
  mimeType: string;
  fileExtension: string;
  formatSpecificFileDownloader?: (
    format: string,
    widget: Widget,
    view: View,
    executionState: SearchExecutionState,
    currentUser: User,
    currentQuery: Query,
    exportPayload: ExportPayload,
  ) => Promise<void>;
}

export interface SystemConfigurationComponentProps<T = unknown> {
  config?: T;
  updateConfig: (newConfig: T) => any;
}

export interface SystemConfiguration {
  skipClusterConfigRequest?: boolean;
  configType: string;
  displayName?: string;
  component: React.ComponentType<SystemConfigurationComponentProps>;
  useCondition?: () => boolean;
  readPermission?: string; // the '?' should be removed once all plugins have a permission config set to enforce it for future plugins right from the beginning
}

export interface CoreSystemConfiguration {
  name: string;
  SectionComponent: React.ComponentType;
  permissions?: Array<string>;
  showCaret?: boolean;
  catchAll?: boolean;
  props?: {
    ConfigurationComponent: React.ComponentType;
    title: string;
  };
}

export type GenericResult = {
  type: string;
  effective_timerange: AbsoluteTimeRange;
  total: number;
};

export type MessageResult = {
  type: 'messages';
  total: number;
  effectiveTimerange: AbsoluteTimeRange;
};

export interface SearchTypeResultTypes {
  generic: GenericResult;
  messages: MessageResult;
}

export interface ActionContexts {
  view: View;
  analysisDisabledFields: Array<string>;
  currentUser: User;
  widget: Widget;
  message: Message;
  valuePath: ValuePath;
  isLocalNode: boolean;
  parameters?: Immutable.Set<Parameter>;
  parameterBindings?: ParameterBindings;
  fieldTypes?: FieldTypeMappingsList;
  toggleFavoriteField?: (field: string) => void;
  favoriteFields?: Array<string>;
}

export type SearchTypeResult = SearchTypeResultTypes[keyof SearchTypeResultTypes];
export type SearchTypeResults = { [id: string]: SearchTypeResult };

export type MessagePreviewOption = {
  title: string;
  isChecked: (config: MessagesWidgetConfig) => boolean;
  isDisabled: (config: MessagesWidgetConfig) => boolean;
  help?: string;
  onChange: (config: MessagesWidgetConfig, onConfigChange: (config: MessagesWidgetConfig) => void) => void;
  sort: number;
};

type ExternalActionsHookData = {
  error: Error | null;
  externalValueActions: Array<ActionDefinition> | null;
  isLoading: boolean;
  isError: boolean;
};

type MessageAugmentation = {
  id: string;
  component: React.ComponentType<{ message: Message }>;
};

type MessageDetailContextProviderProps = {
  message: Message;
};

type DashboardActionComponentProps<T> = {
  dashboard: View;
  modalRef: () => T;
};

type EventWidgetActionComponentProps<T> = {
  eventId: string;
  modalRef: () => T;
};

type DashboardActionModalProps<T> = React.PropsWithRef<{
  dashboard: View;
}> & {
  ref: React.LegacyRef<T>;
};

type EventWidgetActionModalProps<T> = React.PropsWithRef<{
  eventId: string;
}> & {
  ref: React.LegacyRef<T>;
};

export type EventActionModalProps<T> = React.PropsWithRef<{
  events: Array<Event>;
  fromBulk?: boolean;
}> & {
  ref: React.LegacyRef<T>;
};

type SearchActionModalProps = React.PropsWithRef<{
  search: View;
}> & {
  ref: React.LegacyRef<unknown>;
};

type AssetInformationComponentProps = {
  assetIds: unknown;
  direction?: 'col' | 'row';
  addToQuery?: (id: string) => void;
};

type EventProcedureFormProps = {
  eventProcedureId: string | undefined;
  remediationSteps?: string;
  onClose?: () => void;
  onSave?: (eventProcedureId: string) => void;
  onRemove?: (eventProcedureId: string) => void;
};

type EventProcedureSummaryProps = {
  eventProcedureId: string;
  eventId?: string;
  canEdit?: boolean;
  onRemove?: () => void;
  onEdit?: () => void;
  row?: boolean;
};

type SearchAction = {
  component: React.ComponentType<SearchActionComponentProps>;
  key: string;
  modals: Array<{ key: string; component: React.ComponentType<SearchActionModalProps> }>;
  useCondition: () => boolean;
};

type DashboardAction<T> = {
  key: string;
  component: React.ComponentType<DashboardActionComponentProps<T>>;
  modal?: React.ComponentType<DashboardActionModalProps<T>>;
  useCondition?: () => boolean;
};

export type EventAction<T = unknown> = {
  useCondition: (events: Array<Event>) => boolean;
  modal?: React.ComponentType<EventActionModalProps<T>>;
  component: React.ComponentType<EventActionComponentProps<T>>;
  key: string;
  isBulk?: boolean;
};

type EventWidgetAction<T> = {
  key: string;
  component: React.ComponentType<EventWidgetActionComponentProps<T>>;
  modal?: React.ComponentType<EventWidgetActionModalProps<T>>;
  useCondition?: () => boolean;
};

type AssetInformation = {
  component: React.ComponentType<AssetInformationComponentProps>;
  key: string;
};

type EventProcedureForm = {
  component: React.ComponentType<EventProcedureFormProps>;
  key: string;
};

type EventProcedureSummary = {
  component: React.ComponentType<EventProcedureSummaryProps>;
  key: string;
};

type SecurityEventsPage = {
  component: React.ComponentType<{}>;
  key: string;
};

export type EventActionComponentProps<T = unknown> = {
  events: Array<Event>;
  modalRef: () => T;
  fromBulk?: boolean;
};

type MessageActionComponentProps = {
  index: string;
  id: string;
};

type SearchActionComponentProps = {
  loaded: boolean;
  search: View;
  modalRefs?: { [key: string]: () => unknown };
};

export type CopyParamsToView = (sourceView: View, targetView: View) => View;

type RemovingWidgetHook = (widgetId: string, dashboardId: string) => boolean;

interface MessageRowOverrideProps {
  messageFields: Message['fields'];
  config: MessagesWidgetConfig;
  renderMessageRow: () => React.ReactNode;
}

export interface CombinedSearchBarFormValues {
  timerange?: TimeRange | NoTimeRangeOverride;
  streams?: Array<string>;
  streamCategories?: Array<string>;
  queryString?: string;
}

export interface HandlerContext {
  view: View;
  executionState: SearchExecutionState;
}

export interface SearchBarControl {
  component: React.ComponentType;
  id: string;
  onSearchSubmit?: <T extends Query | undefined>(
    values: CombinedSearchBarFormValues,
    dispatch: ViewsDispatch,
    currentQuery?: T,
  ) => Promise<T>;
  onDashboardWidgetSubmit: (
    values: CombinedSearchBarFormValues,
    dispatch: ViewsDispatch,
    currentWidget: Widget,
  ) => Promise<Widget | void>;
  onValidate?: (values: CombinedSearchBarFormValues, context?: HandlerContext) => FormikErrors<{}>;
  placement: 'left' | 'right';
  useInitialSearchValues?: (currentQuery?: Query) => { [key: string]: any };
  useInitialDashboardWidgetValues?: (currentWidget: Widget) => { [key: string]: any };
  validationPayload?: (values: CombinedSearchBarFormValues, context?: HandlerContext) => { [key: string]: any };
}

export type SearchFilter = {
  type: 'referenced' | 'inlineQueryString';
  id?: string;
  title?: string;
  description?: string;
  queryString: string;
  negation?: boolean;
  disabled?: boolean;
};

export type FiltersType = Immutable.List<SearchFilter>;

export type SaveViewControls = {
  component: React.ComponentType<{ disabledViewCreation?: boolean }>;
  id: string;
  onSearchDuplication?: (view: View, userPermissions: Immutable.List<string>) => Promise<View>;
  onDashboardDuplication?: (view: View, userPermissions: Immutable.List<string>) => Promise<View>;
};

export type CustomCommandContextProvider<T extends keyof CustomCommandContext> = {
  key: T;
  provider: () => CustomCommandContext[T];
};

export type CurrentViewType = {
  activeQuery: string;
};

export interface ViewState {
  activeQuery: QueryId;
  view: View;
  isDirty: boolean;
  isNew: boolean;
}

export type SearchExecutionResult = {
  result: SearchResult;
  widgetMapping?: WidgetMapping;
};

export type JobIdsState = JobIds | null;
export interface SearchExecution {
  executionState: SearchExecutionState;
  result: SearchExecutionResult;
  isLoading: boolean;
  searchTypesToSearch: Array<string>;
  jobIds?: JobIds | null;
}

export interface SearchMetadataState {
  isLoading: boolean;
  metadata: SearchMetadata;
}

export interface RootState {
  view: ViewState;
  searchExecution: SearchExecution;
  searchMetadata: SearchMetadataState;
  undoRedo: UndoRedoState;
  widgets: WidgetsState;
}

export interface ExtraArguments {
  searchExecutors: SearchExecutors;
}

export type GetState = () => RootState;

export type ViewsReducer = PluggableReducer<RootState>;

export type Widgets = Immutable.OrderedMap<string, Widget>;

export interface WidgetCreatorArgs {
  view: View;
}
export interface WidgetCreator {
  title: string;
  func: (args: WidgetCreatorArgs) => Widget;
  icon: React.ComponentType<{}>;
}

export type FieldUnitType = 'size' | 'time' | 'percent';

export type DefaultAxisKey = typeof DEFAULT_AXIS_KEY;

export type FieldUnitsFormValues = Record<string, { abbrev: string; unitType: FieldUnitType }>;

export type SearchDataSource = {
  key: string;
  title: string;
  icon: IconName;
  link: string;
  useCondition: () => boolean;
};

type LICENSE_SUBJECTS = {
  enterprise: '/license/enterprise';
  archive: '/license/enterprise/archive';
  auditlog: '/license/enterprise/auditlog';
  illuminate: '/license/enterprise/illuminate';
  searchFilter: '/license/enterprise/search-filter';
  customization: '/license/enterprise/customization';
  views: '/license/enterprise/views';
  forwarder: '/license/enterprise/forwarder';
  report: '/license/enterprise/report';
  security: '/license/security';
  anomaly: '/license/anomaly';
};

export type LicenseSubject = LICENSE_SUBJECTS[keyof LICENSE_SUBJECTS];

export type LicenseCheck = (subject: LicenseSubject) => {
  data: {
    valid: boolean;
    expired: boolean;
    violated: boolean;
  };
  isInitialLoading: boolean;
  refetch: () => void;
};

type MarkdownAugmentation = {
  id: string;
  component: React.ComponentType<{ value: string }>;
};

declare module 'graylog-web-plugin/plugin' {
  export interface PluginExports {
    creators?: Array<Creator>;
    enterpriseWidgets?: Array<WidgetExport>;
    useExternalActions?: Array<() => ExternalActionsHookData>;
    fieldActions?: Array<ActionDefinition>;
    fieldTypeValueRenderer?: Array<{
      type: string;
      render: (value: unknown, field: string, render: React.ComponentType<ValueRendererProps>) => React.ReactNode;
    }>;
    messageAugmentations?: Array<MessageAugmentation>;
    searchTypes?: Array<SearchType<any, any>>;
    coreSystemConfigurations?: Array<CoreSystemConfiguration>;
    systemConfigurations?: Array<SystemConfiguration>;
    valueActions?: Array<ActionDefinition>;
    'views.completers'?: Array<Completer>;
    'views.components.assetInformationActions'?: Array<AssetInformation>;
    'views.components.eventProcedureForm'?: Array<EventProcedureForm>;
    'views.components.eventProcedureSummary'?: Array<EventProcedureSummary>;
    'views.components.securityEventsPage'?: Array<SecurityEventsPage>;
    'views.components.dashboardActions'?: Array<DashboardAction<unknown>>;
    'views.components.eventActions'?: Array<EventAction<unknown>>;
    'views.components.widgets.messageTable.previewOptions'?: Array<MessagePreviewOption>;
    'views.components.widgets.messageTable.messageRowOverride'?: Array<React.ComponentType<MessageRowOverrideProps>>;
    'views.components.widgets.messageDetails.contextProviders'?: Array<
      React.ComponentType<React.PropsWithChildren<MessageDetailContextProviderProps>>
    >;
    'views.components.widgets.messageTable.contextProviders'?: Array<React.ComponentType<React.PropsWithChildren<{}>>>;
    'views.components.widgets.messageTable.messageActions'?: Array<{
      component: React.ComponentType<MessageActionComponentProps>;
      key: string;
      useCondition: () => boolean;
    }>;
    'views.components.widgets.events.filterComponents'?: FilterComponents;
    'views.components.widgets.events.attributes'?: Attributes;
    'views.components.widgets.events.detailsComponent'?: Array<{
      component: React.ComponentType<{ eventId: string }>;
      useCondition: () => boolean;
      key: string;
    }>;
    'views.components.widgets.events.actions'?: Array<EventWidgetAction<unknown>>;
    'views.components.searchActions'?: Array<SearchAction>;
    'views.components.searchBar'?: Array<() => SearchBarControl | null>;
    'views.components.saveViewForm'?: Array<() => SaveViewControls | null>;
    'views.elements.header'?: Array<React.ComponentType>;
    'views.elements.aside'?: Array<React.ComponentType>;
    'views.elements.queryBar'?: Array<React.ComponentType>;
    'views.elements.validationErrorExplanation'?: ValidationExplanations;
    'views.export.formats'?: Array<ExportFormat>;
    'views.hooks.confirmDeletingDashboard'?: Array<(view: View) => Promise<boolean | null>>;
    'views.hooks.confirmDeletingDashboardPage'?: Array<
      (dashboardId: string, queryId: string, widgetIds: { [queryId: string]: Array<string> }) => Promise<boolean | null>
    >;
    'views.hooks.confirmDeletingWidget'?: Array<(widget: Widget, view: View, title: string) => Promise<boolean | null>>;
    'views.hooks.executingView'?: Array<ViewHook>;
    'views.hooks.loadingView'?: Array<ViewHook>;
    'views.hooks.copyWidgetToDashboard'?: Array<CopyParamsToView>;
    'views.hooks.copyPageToDashboard'?: Array<CopyParamsToView>;
    'views.hooks.removingWidget'?: Array<RemovingWidgetHook>;
    'views.overrides.widgetEdit'?: Array<React.ComponentType<OverrideProps>>;
    'views.searchDataSources'?: Array<SearchDataSource>;
    'views.widgets.actions'?: Array<WidgetActionType>;
    'views.widgets.exportAction'?: Array<{ action: WidgetActionType; useCondition: () => boolean }>;
    'views.reducers'?: Array<ViewsReducer>;
    'views.requires.provided'?: Array<string>;
    'views.queryInput.commands'?: Array<CustomCommand>;
    'views.queryInput.commandContextProviders'?: Array<CustomCommandContextProvider<any>>;
    visualizationTypes?: Array<VisualizationType<any>>;
    widgetCreators?: Array<WidgetCreator>;
    'licenseCheck'?: Array<LicenseCheck>;
    entityPermissionsMapper?: EntityPermissionsMapper;
    'markdown.augment.components'?: Array<MarkdownAugmentation>;
  }
}
